#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <UIKit/UIApplication.h>
#import <LockInfo/Plugin.h>

#import "framework/FWCommon.h"
#import "framework/FWApplication.h"
#import "framework/FWApplicationDelegate.h"
#import "framework/FWPreferences.h"
#import "framework/FWListView.h"
#import "framework/FWDetailListView.h"
#import "framework/FWApplication.h"

#import "PIIntegration.h"
#import "PITaskIntegration.h"
#import "PITaskNative.h"
#import "PITask.h"

#import "PIPlugin.h"
#import "PICommon.h"
#import "PITaskListAccess.h"
#import "PITaskView.h"
#import "PITaskPreview.h"
#import "PITaskStatus.h"
#import "PITaskPlugin.h"
#import "PIContext.h"


@implementation PITaskListAccess


static NSInteger defaultContextUpdates = kPITaskOptionsDefaultContextsUpdatesDefault;


// ******************************************************************************************
#pragma mark -
#pragma mark Fetching data from the database

- (NSArray*)fetchNewDataForPrefs:(FWPreferences*)prefs application:(FWApplication*)application
{
    NSBundle* bundle = [NSBundle bundleForClass:[self class]];
    PITaskStatus* status = (PITaskStatus*)application.info;
    PITaskPlugin* plugin = (PITaskPlugin*)application.fwPlugin;

    // Remove error messages from screen
    [application.fwPlugin resetMessageBox];

    // Ensure the statusbar is visible if necessary
    [application.fwPlugin setStatusbarVisible:[prefs boolValue:kPIOptionsShowToolbar withDefault:kPIOptionsShowToolbarDefault]];
    
    // Read current display settings
    application.fwPlugin.debugMode = ([prefs intValue:kPIOptionsDebugCode withDefault:kPIOptionsDebugCodeDefault] == kDebugModeCode);
    status.shortDates = [prefs intValue:kPIOptionsShortDates withDefault:kPIOptionsShortDatesDefault];
    status.showIcons = [prefs boolValue:kPIOptionsShowIcons withDefault:kPIOptionsShowIconsDefault];
    status.showContexts = [prefs intValue:kPIOptionsShowContexts withDefault:kPIOptionsShowContextsDefault];
    status.singleLineOniPad = [prefs boolValue:kPIOptionsiPadSingleLine withDefault:kPIOptionsiPadSingleLineDefault];
    status.showEmoji = [prefs boolValue:kPIOptionsShowEmoji withDefault:kPIOptionsShowEmojiDefault]; 
    status.emojiAfter = [prefs boolValue:kPIOptionsEmojiAfter withDefault:kPIOptionsEmojiAfterDefault]; 
    status.lowColour = [prefs boolValue:kPIOptionsLowColourDisplay withDefault:kPIOptionsLowColourDisplayDefault];
    status.showSourceIcons = [prefs intValue:kPIOptionsShowSourceIcons withDefault:kPIOptionsShowSourceIconsDefault];
    
    plugin.eventStoreUpdated = NO;    // Mark as no longer updated

    BOOL debug = plugin.isDebugMode;

    // Save the current setting of the show details flag
    NSInteger showDetails = [prefs intValue:kPIOptionsShowDates withDefault:kPIOptionsShowDatesDefault];
    if (showDetails != status.showListDetails) {
        DebugLog(@"LI:PITask: Details display changed - reset toggle mode");
        status.toggleOn = NO;
        status.showListDetails = showDetails;
    }
    
    // If using automatic contexts then get the latest values
    BOOL contextMode = status.autoContextOn || status.fixedContextOn;
    if (status.isAutoContextOn) {
        // Update the context update count if there is one
        NSInteger changeCount = -1;
        if (UIPasteboard* pasteBoard = [UIPasteboard pasteboardWithName:PIIntegrationContextPasteboard create:NO])
            changeCount = [pasteBoard changeCount];
        
        // If change count has changed then record the new value and get the latest contexts
        if (changeCount != status.autoContextPasteboardUpdateCount) {
            status.autoContextPasteboardUpdateCount = changeCount;
            [application setAttribute:kPIApplicationContextLocation value:[PIIntegration locationContextsFromPI]];
        }
    }
    
    FWDatabase* database = nil;
    @try {
        // Connect to the database once only
        if (plugin.isPocketInformantPresent && !plugin.isNativeOnly)
            database = FWDBConnectDatabase(application.databasePath, application);
        
        // Check if default contexts need rebuilding
        NSInteger newDefaultContextUpdates = [prefs intValue:kPITaskOptionsDefaultContextsUpdates
                                                 withDefault:kPITaskOptionsDefaultContextsUpdatesDefault];
        if (defaultContextUpdates != newDefaultContextUpdates) {
            XLog(@"LI:PITask: Default context list has been updated - rebuilding");

            NSMutableArray* newDefaults = [NSMutableArray arrayWithCapacity:10];
            if (NSArray* defaults = [prefs arrayValue:kPITaskOptionsDefaultContexts withDefault:nil]) {
                NSArray* contexts = [PITaskIntegration arrayOfContexts:application earliest:nil counts:NO];
                for (NSString* key in defaults) {
                    if (PIContext* context = [contexts piContextForKey:key.intValue])
                        [newDefaults addObject:context];
                }
            }
            
            XLog(@"LI:PITodo: New default context list = %@", newDefaults);

            [application setAttribute:kPIApplicationContextDefault value:newDefaults];
            defaultContextUpdates = newDefaultContextUpdates;
        }
        
        
        // Get the current data
        NSMutableArray* newTodos = [PITaskIntegration basicTaskList:application database:database
                                                              prefs:prefs key:nil parentKey:nil search:nil];

        // Add any reminders from iOS
        NSMutableArray* iosTodos = [PITaskNative basicTaskList:application database:database prefs:prefs key:nil search:nil];
        if (iosTodos.count > 0) {
            NSArray* order = [NSArray arrayWithObjects:
                              [NSArray arrayWithObjects:
                               [NSNumber numberWithInt:[prefs intValue:kPIOptionsSortField1
                                                           withDefault:kPIOptionsSortField1Default]],
                               [NSNumber numberWithBool:[prefs intValue:kPIOptionsSortField1Asc
                                                            withDefault:kPIOptionsSortField1AscDefault]],
                               [NSNumber numberWithInt:[prefs intValue:kPIOptionsSortField1Order
                                                           withDefault:kPIOptionsSortField1OrderDefault]],
                               [NSNumber numberWithInt:[prefs intValue:kPIOptionsMissingImportance
                                                           withDefault:kPIOptionsMissingImportanceDefault]],
                               nil],
                              [NSArray arrayWithObjects:
                               [NSNumber numberWithInt:[prefs intValue:kPIOptionsSortField2
                                                           withDefault:kPIOptionsSortField2Default]],
                               [NSNumber numberWithBool:[prefs intValue:kPIOptionsSortField2Asc
                                                            withDefault:kPIOptionsSortField2AscDefault]],
                               [NSNumber numberWithInt:[prefs intValue:kPIOptionsSortField2Order
                                                           withDefault:kPIOptionsSortField2OrderDefault]],
                               [NSNumber numberWithInt:[prefs intValue:kPIOptionsMissingImportance
                                                           withDefault:kPIOptionsMissingImportanceDefault]],
                               nil],
                              [NSArray arrayWithObjects:
                               [NSNumber numberWithInt:SortFieldOptionsTitle],
                               NSNumberYES,
                               [NSNumber numberWithInt:SortFieldNoneOrderFirst],
                               [NSNumber numberWithInt:kPIOptionsMissingImportanceDefault],
                               nil],
                              nil];
            [iosTodos sortUsingFunction:compareNativeTasks context:order];
            
            NSInteger maxiOSItems = [prefs intValue:kPIOptionsMaxTasks withDefault:kPIOptionsMaxTasksDefault];
            for (PITask* task in iosTodos) {
                NSUInteger count = newTodos.count;
                NSInteger insertPos = -1;
                for (NSUInteger index = 0; index < count; ++index) {
                    PITask* test = [newTodos objectAtIndex:index];
                    if (compareNativeTasks(task, test, order) != NSOrderedDescending) {
                        insertPos = index;
                        break;
                    }
                }
                
                if (insertPos >= 0)
                    [newTodos insertObject:task atIndex:insertPos];
                else
                    [newTodos addObject:task];
                
                if (--maxiOSItems <= 0)
                    break;
            }
        }        

        NSString* contextCaption = nil;
        if (contextMode) {
            NSMutableArray* contextList = [NSMutableArray arrayWithCapacity:20];
            if (status.isAutoContextOn) {
                [contextList addUniqueObjectsFromArray:[application getAttribute:kPIApplicationContextLocation]];
                if (contextList.count == 0)
                    [contextList addUniqueObjectsFromArray:[application getAttribute:kPIApplicationContextDefault]];
            }

            if (status.isFixedContextOn)
                [contextList addUniqueObjectsFromArray:[application getAttribute:kPIApplicationContextFixed]];
            
            if (contextList.count > 0) {
                NSMutableString* contextNames = [NSMutableString stringWithCapacity:80];
                for (PIContext* context in contextList) {
                    [contextNames appendString:context.title withSeparator:@", "];
                }
                
                if (contextNames.length > 0)
                    contextCaption = contextNames;
            }
            else
                contextMode = NO;
            
            if (!contextCaption)
                contextCaption = localize(bundle, @"Context/Label/None");
        }

        if (contextCaption) {
            NSString* oldCaption = status.captionValue;
            status.captionValue = contextCaption;
            
            // Update the caption if it has changed
            if (!oldCaption || ![oldCaption isEqualToString:contextCaption])
                [application.fwPlugin updateStatusbar];
        }
        else {
            if (status.captionValue) {
                // Remove caption value as we don't have one any more
                status.captionValue = nil;
                [application.fwPlugin updateStatusbar];
            }
        }
        
        // Examine the data set
        BOOL contextFound = NO;
        NSInteger returnedCount = [newTodos count];
        NSInteger filteredCount = 0;
        NSInteger detailsCount = 0;
        
        // Check if any of the selected contexts were found in the dataset
        for (PITask* element in newTodos) {
            if (element.contextClassification == ContextClassificationInContext)
                contextFound = YES;
            
            if (element.hasDetails)
                ++detailsCount;
        }
        
        NSInteger hideNoContext = [prefs intValue:kPIOptionsHideNoContext withDefault:kPIOptionsHideNoContextDefault];
        NSInteger hideOutOfContext = [prefs intValue:kPIOptionsHideOutOfContext withDefault:kPIOptionsHideOutOfContextDefault];
        BOOL showAllTasks = [prefs boolValue:kPIOptionsShowAllTasks withDefault:kPIOptionsShowAllTasksDefault];
        
        if (contextFound ||
            (contextMode && ((hideNoContext == FilterContextOptionsHideAlways) || (hideOutOfContext == FilterContextOptionsHideAlways)))) {
            NSMutableArray* filteredList = [NSMutableArray arrayWithCapacity:[newTodos count]];
            NSMutableArray* lastList = [NSMutableArray arrayWithCapacity:[newTodos count]];
            BOOL contextAtStart = [prefs boolValue:kPIOptionsShowContextAtStart withDefault:kPIOptionsShowContextAtStartDefault];
            
            if (debug)
                XLog(@"LI:PITask: Applying context mode filters...");
            
            for (PITask* element in newTodos) {
                NSInteger context = element.contextClassification;
                BOOL filter = NO;
                
                if (context == ContextClassificationInContext) {
                    // Do nothing
                }
                else if (context == ContextClassificationOutOfContext) {
                    if (hideOutOfContext == FilterContextOptionsHideAlways) {
                        if (debug)
                            XLog(@"LI:PITask: Filtering out of context always: %@", element.shortDescription);
                        filter = YES;
                    }
                    else if (hideOutOfContext == FilterContextOptionsHideIfMatch) {
                        if (debug)
                            XLog(@"LI:PITask: Filtering out of context since match found: %@", element.shortDescription);
                        filter = contextFound;
                    }
                }
                else if (context == ContextClassificationNoContext) {
                    if (hideNoContext == FilterContextOptionsHideAlways) {
                        if (debug)
                            XLog(@"LI:PITask: Filtering no context always: %@", element.shortDescription);
                        filter = YES;
                    }
                    else if (hideNoContext == FilterContextOptionsHideIfMatch) {
                        if (debug)
                            XLog(@"LI:PITask: Filtering no context since match found: %@", element.shortDescription);
                        filter = contextFound;
                    }
                }
                
                if (filter && !showAllTasks) {
                    // Update totals to indicate am item is now missing
                    if (element.hasDetails)
                        --detailsCount;
                    ++filteredCount;
                }
                else {
                    if ((context == ContextClassificationInContext) || !contextAtStart)
                        [filteredList addObject:element];
                    else
                        [lastList addObject:element];
                }
            }
            
            // If we have any items we've moved down the list then add them back to the end
            if ([lastList count] > 0)
                [filteredList addObjectsFromArray:lastList];
            
            if (debug)
                XLog(@"LI:PITask: Filtered task list; old size = %d; new size = %d", newTodos.count, filteredList.count);
            
            // Use the filtered list
            newTodos = filteredList;
        }
        
        [PITaskIntegration addAlarmDataToTaskList:application database:database tasks:newTodos prefs:prefs];
        
        status.itemsReturnedCount = returnedCount;
        status.itemsWithDetailsCount = detailsCount;
        status.itemsFilteredCount = filteredCount;

        if (debug)
            XLog(@"LI:PITask: New task list: %@", newTodos);

        [(PITaskPlugin*)application.fwPlugin updatePrefrences:prefs];

        // Use the original data list
        return newTodos;
    }
    @catch (NSException *exception) {
        NSLog(@"LI:%@: Exception; %@", application.resourceName, exception);
        
        [application.fwPlugin setMessageBox:@"Exception while building task list"
                                      line2:exception.name
                                     detail:[NSString stringWithFormat:@"%@\n----------\n%@",
                                             exception.reason,
                                             [exception.callStackSymbols description]]];

        return [NSArray array];
    }
    @finally {
        FWDBDisconnectDatabase(database);
    }
    
    // Could not access database
    return [NSArray array];
}

- (BOOL)shouldUpdateForDate:(NSDate *)date application:(FWApplication *)app
{
    if (((PITaskStatus*)app.info).isAutoContextOn) {
        if (UIPasteboard* pasteBoard = [UIPasteboard pasteboardWithName:PIIntegrationContextPasteboard create:NO]) {
            NSInteger lastUpdates = ((PITaskStatus*)app.info).autoContextPasteboardUpdateCount;
            NSInteger newUpdates = [pasteBoard changeCount];
            if (newUpdates > lastUpdates) {
                XLog(@"LI:PITask: Context list changed - old=%i, new=%i - update required", lastUpdates, newUpdates);
                return YES;
            }
        }
    }
    
    if (((PITaskPlugin*)app.fwPlugin).isEventStoreUpdated) {
        XLog(@"LI:PITask: Event store has been updated");
        return YES;
    }
    
    return NO;
}    


// ******************************************************************************************
#pragma mark -
#pragma mark Describing the layout

- (BOOL)shouldShowDetailLine:(PITask*)task application:(FWApplication*)app preferences:(FWPreferences*)prefs forItem:(BOOL)forItem
{
    int showDate = ((PITaskStatus*)app.info).showListDetails;
    BOOL toggleActive = ((PITaskStatus*)app.info).isToggleOn;

    if (showDate == ShowDatesAlways) {
        // If always showing dates then show unless the toggle is present
        return !toggleActive;
    }
    else if (showDate == ShowDatesNever) {
        // If never showing dates then don't show unless the toggle is present
        return toggleActive;
    }
    else if (toggleActive) {
        // Field is optional. If toggle is active then if no details were present for any
        // item then withoug the toggle, nothing was shown. So, show all details as the toggle
        // state. Otherwise show no details as the toggle state as some items must have
        // been shown without the toggle.
        return ((PITaskStatus*)app.info).itemsWithDetailsCount == 0;
    }
    
    if (forItem)
        return task.hasDetails;
    else
        return NO;
}

- (CGFloat)heightForItem:(NSObject *)item theme:(LITheme *)theme prefs:(FWPreferences *)prefs application:(FWApplication *)app
{
    PITask* task = (PITask*)item;
    BOOL showDetail = [self shouldShowDetailLine:task application:app preferences:prefs forItem:YES];
    BOOL singleLine = ((PITaskStatus*)app.info).isSingleLineOniPad;
    
    if (FWIsDeviceAnIPad() && singleLine) {
        return [PITaskView heightOfSummaryRowWithFont:theme.summaryStyle.font
                                           withHeader:NO
                                     withFollowingRow:NO];
    }
    else {
        CGFloat summaryHeight = [PITaskView heightOfSummaryRowWithFont:theme.summaryStyle.font
                                                            withHeader:NO
                                                      withFollowingRow:showDetail];

        if (showDetail) {
            CGFloat detailHeight = [PITaskView heightOfDetailRowWithFont:theme.detailStyle.font];
            return summaryHeight + detailHeight;
        }
        else
            return summaryHeight;
    }
}


// ******************************************************************************************
#pragma mark -
#pragma mark Providing data for the display

- (FWListView*)viewForTheme:(LITheme*)theme existing:(UIView*)existing prefs:(FWPreferences*)prefs application:(FWApplication*)app
{
    if ([existing isKindOfClass:[PITaskView class]])
        return (PITaskView*)existing;
    else
        return [[[PITaskView alloc] initWithFrame:CGRectMake(0, 0, 320, 0)
                                            theme:theme
                                      application:app] autorelease];
}

- (void)setDetailsUsingView:(FWListView *)view forItem:(NSObject *)item theme:(LITheme *)theme prefs:(FWPreferences *)prefs application:(FWApplication *)app
{
    PITaskStatus* status = (PITaskStatus*)app.info;
    
    if (item) {
        PITask* task = (PITask*)item;
        
        BOOL dateLine = [self shouldShowDetailLine:task application:app preferences:prefs forItem:YES];
        BOOL showOverdue = [prefs boolValue:kPIOptionsHighlightOverdue withDefault:kPIOptionsHighlightOverdueDefault];
        BOOL showChildIcons = [prefs boolValue:kPIOptionsShowParentIcons withDefault:kPIOptionsShowParentIconsDefault];
        kPITaskViewStyle style = (kPITaskViewStyle)[prefs intValue:kPITaskOptionsViewStyle withDefault:kPITaskOptionsViewStyleDefault];
        
        [(PITaskView*)view setViewForTask:task
                                    theme:theme
                              application:app
                                    style:style
                           showShortDates:(ShortDatesOptions)status.shortDates
                           showDetailLine:dateLine
                                showIcons:status.isShowIcons
                                showEmoji:status.isShowEmoji
                              showOverdue:showOverdue
                           showChildIcons:showChildIcons
                                lowColour:status.isLowColour
                               sourceIcon:(ShowSourceIconOptions)status.showSourceIcons
                               emojiAfter:status.emojiAfter];
        
        if ([app.fwPlugin accessoryActionType:task] == FWAccessoryActionTypeNone)
            view.rightReservedSpace = 0;
        else
            view.rightReservedSpace = 24;
        
        // Do we want everything on one line for an iPad?
        if ((view.singleLine = FWIsDeviceAnIPad()) && status.isSingleLineOniPad) {
            if (status.showListDetails != ShowDatesIfNeeded)
                view.expandSingleLine = ![self shouldShowDetailLine:task application:app preferences:prefs forItem:NO];
            else if (status.isToggleOn)
                view.expandSingleLine = status.itemsWithDetailsCount != 0;
            else
                view.expandSingleLine = status.itemsWithDetailsCount == 0;
        }
    }
    else {
        PITaskPlugin* todo = (PITaskPlugin*)app.fwPlugin;
        view.summaryText.text = [NSString stringWithFormat:@"⚡ %@", FWDebugTimeStringForUTCDate(todo.lastUpdate)];
        view.detailText.text = [NSString stringWithFormat:@"items=%i; filtered=%i; details=%i",
                                status.itemsReturnedCount,
                                status.itemsFilteredCount,
                                status.itemsWithDetailsCount];
    }
}

- (NSString*)detailForHeader:(FWPreferences*)prefs application:(FWApplication*)app
{
    NSBundle* bundle = [NSBundle bundleForClass:[self class]];
    NSInteger showContexts = ((PITaskStatus*)app.info).showContexts;
    
    if ([prefs boolValue:kPIOptionsShowAllTasks withDefault:kPIOptionsShowAllTasksDefault])
        return localize(bundle, @"Filters/Disabled");
    
    if (showContexts == ShowContextsNever)
        return nil;
    else if (((PITaskStatus*)app.info).isAutoContextOn) {
        NSArray* contexts = (NSArray*)[app getAttribute:kPIApplicationContextLocation];
        if (!contexts || ([contexts count] == 0))
            return localize(bundle, @"Context/Auto/0");
        else if ([contexts count] == 1)
            return localize(bundle, @"Context/Auto/1");
        else
            return [NSString stringWithFormat:localize(bundle, @"Context/Auto/X"), [contexts count]]; 
    }
    else if (((PITaskStatus*)app.info).isFixedContextOn) {
        NSArray* contexts = (NSArray*)[app getAttribute:kPIApplicationContextFixed];
        if (!contexts || ([contexts count] == 0))
            return localize(bundle, @"Context/Manual/0");
        else if ([contexts count] == 1)
            return localize(bundle, @"Context/Manual/1");
        else
            return [NSString stringWithFormat:localize(bundle, @"Context/Manual/X"), [contexts count]]; 
    }
    else if (showContexts == ShowContextsAlways)
        return localize(bundle, @"Context/Off");
    else 
        return nil;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Implement preview display

- (UIView*)previewWithFrame:(CGRect)rect atIndex:(NSInteger)index forItems:(NSArray *)list theme:(LITheme *)theme prefs:(FWPreferences *)prefs application:(FWApplication *)app
{   
    LIPlugin* plugin = (LIPlugin*)[app.fwPlugin liPlugin];

    // Are previews disabled?
    if (![FWPreferences boolValue:kPIOptionsShowPreviews withDefault:kPIOptionsShowPreviewsDefault preferences:plugin.preferences])
        return nil;

    // Don't display on the lockscreen
    if (app.isLockScreen && ![FWPreferences boolValue:kPIOptionsLockPreview withDefault:kPIOptionsLockPreviewDefault preferences:plugin.preferences])
        return nil;

    PITaskPreview* previewData = [[[PITaskPreview alloc] initWithApplication:app] autorelease];
    FWDetailListView *thePreview = [[[FWDetailListView alloc] initWithFrame:rect
                                                                       list:list
                                                                      index:index
                                                                    caption:@"ListView/Item %i/%i"
                                                                application:app
                                                                   delegate:previewData] autorelease];
    previewData.controlDelegate = thePreview;
    
    return thePreview;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Libstatusbar support

- (NSString*)libstatusbarIconName
{
    return @"PIPlugin";
}

- (BOOL)showLibstatusbarIconForItems:(NSArray*)items prefs:(FWPreferences *)prefs
{
    if ([prefs boolValue:kPIOptionsShowStatusbarIcons withDefault:kPIOptionsShowStatusbarIconsDefault]) {
        for (PITask* task in items) {
            if (task.isInProgress)
                return YES;
        }
    }
    
    return NO;
}

@end
